using System;
using System.Data;
using System.Text;
using System.Collections;

using BizElements.Core.ProviderComponents;

namespace BizElements.Core
{
    /// <summary>Specifies how argument is handled and passed to stored procedures.</summary>
    public enum SpArgumentPassMode
    {
        /// <summary>Standard input, output or in/out parameter supported by all databases whose type is unambigously defined by <see cref="System.Data.DbType"/> enumeration.</summary>
        ArgumentDbType,
        /// <summary>Output parameter represented by <see cref="System.Data.DataTable"/> filled by <see cref="System.Data.IDataAdapter"/>. Example: Oracle cursor.</summary>
        DataTableFilledByAdapter
    }

	/// <summary>
	/// Represents an SQL command parameter. Converted to appropriate IDbDataParameter object during the command execution.
	/// </summary>
	[Serializable]
	public sealed class DbParameter : IDataParameter, IRenderSql
	{
		#region Members.

		string uniqueName;		
		int size;
		ParameterDirection direction = ParameterDirection.Input;
		bool isNullable = true;
		byte precision;
		byte scale;
		string sourceColumn;
		DataRowVersion sourceVersion = DataRowVersion.Proposed;
		object value;		
		
		string originalName;
		string renderedName;
        DbType dbType;
        SpArgumentPassMode passMode = SpArgumentPassMode.ArgumentDbType;        

		#endregion

		#region Constructors.

		/// <summary>
		/// Initializes a new instance of the DbParameter class.
		/// </summary>
		public DbParameter()
		{
		}

		/// <summary>
		/// Initializes a new instance of the DbParameter class.
		/// </summary>
		/// <param name="name">The name of the parameter to map. <b>Null</b> is not allowed.</param>
		/// <param name="dbType">One of the <see cref="DbType"/> values.</param>
		public DbParameter(string name, DbType dbType)
		{			
			// Property validates given value and sets both the uniqueName and originalName members.
			this.ParameterName = name;

			this.dbType = dbType;
		}

		/// <summary>
		/// Initializes a new instance of the DbParameter class.
		/// </summary>
		/// <param name="name">The name of the parameter to map. <b>Null</b> is not allowed.</param>
		/// <param name="dbType">One of the <see cref="DbType"/> values.</param>
		/// <param name="value">An Object that is the value of the DbParameter.</param>
		/// <param name="direction">One of the <see cref="ParameterDirection"/> values.</param>
		public DbParameter(string name, DbType dbType, object value, ParameterDirection direction)
		{
			// Property validates given value and sets both the uniqueName and originalName members.
			this.ParameterName = name;

			this.dbType = dbType;
			this.value = value;
			this.direction = direction;
		}

        /// <summary>
        /// Initializes a new instance of the DbParameter class.
        /// </summary>
        /// <param name="name">The name of the parameter to map. <b>Null</b> is not allowed.</param>
        /// <param name="dbType">One of the <see cref="DbType"/> values.</param>
        /// <param name="size">The length of the parameter.</param>
        /// <param name="direction">One of the <see cref="ParameterDirection"/> values.</param>
        /// <param name="isNullable">True if the value of the field can be null, otherwise false.</param>
        /// <param name="precision">The total number of digits to the left and right of the decimal point to which <see cref="Value"/> is resolved.</param>
        /// <param name="scale">The total number of decimal places to which <see cref="Value"/> is resolved.</param>
        /// <param name="sourceColumn">The name of the source column.</param>
        /// <param name="sourceVersion">One of the <see cref="DataRowVersion"/> values.</param>
        /// <param name="value">An Object that is the value of the DbParameter.</param>
        public DbParameter(string name, DbType dbType, int size, ParameterDirection direction, bool isNullable, byte precision, byte scale, string sourceColumn, DataRowVersion sourceVersion, object value)
        {
            // Property validates given value and sets both the uniqueName and originalName members.
            this.ParameterName = name;

            this.dbType = dbType;
            this.size = size;
            this.direction = direction;
            this.isNullable = isNullable;
            this.precision = precision;
            this.scale = scale;
            this.sourceColumn = sourceColumn;
            this.sourceVersion = sourceVersion;
            this.value = value;
        }

		/// <summary>
		/// Initializes a new instance of the DbParameter class.
		/// </summary>
		/// <param name="name">The name of the parameter to map. <b>Null</b> is not allowed.</param>
		/// <param name="dbType">One of the <see cref="DbType"/> values.</param>
		/// <param name="size">The length of the parameter.</param>
		/// <param name="direction">One of the <see cref="ParameterDirection"/> values.</param>
		/// <param name="isNullable">True if the value of the field can be null, otherwise false.</param>
		/// <param name="precision">The total number of digits to the left and right of the decimal point to which <see cref="Value"/> is resolved.</param>
		/// <param name="scale">The total number of decimal places to which <see cref="Value"/> is resolved.</param>
		/// <param name="sourceColumn">The name of the source column.</param>
		/// <param name="sourceVersion">One of the <see cref="DataRowVersion"/> values.</param>
		/// <param name="value">An Object that is the value of the DbParameter.</param>
        /// <param name="passMode">Specifies how argument is handled and passed to stored procedures.</param>
        public DbParameter(string name, DbType dbType, int size, ParameterDirection direction, bool isNullable, byte precision, byte scale, string sourceColumn, DataRowVersion sourceVersion, object value, SpArgumentPassMode passMode)
		{
			// Property validates given value and sets both the uniqueName and originalName members.
			this.ParameterName = name;

			this.dbType = dbType;
			this.size = size;
			this.direction = direction;
			this.isNullable = isNullable;
			this.precision = precision;
			this.scale = scale;
			this.sourceColumn = sourceColumn;
			this.sourceVersion = sourceVersion;
			this.value = value;
            this.passMode = passMode;
		}

		#endregion

		#region Properties.

		/// <summary>
		/// Gets or sets the name of the parameter. Setter throws exception if given value is <b>null</b>.
		/// </summary>
		/// <remarks>Note: the parameter name may be changed during the rendering phase if it's not unique.
		/// If there are two parameters names "MyParameter" then one of them will be changed to "MyParameter_2".</remarks>
		public string ParameterName
		{
			get {return this.uniqueName;}
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value", Messages.DbParameter_NameMayNotBeNull);

                this.uniqueName = value;
                this.originalName = value;
            }
		}

		/// <summary>
		/// Gets or sets the <see cref="System.Data.DbType"/> of the parameter.
		/// </summary>
		public DbType DbType
		{
			get {return this.dbType;}
			set {this.dbType = value;}
		}

		/// <summary>
		/// Gets or sets the size of the parameter.
		/// </summary>
		public int Size
		{
			get {return this.size;}
			set {this.size = value;}
		}

		/// <summary>
		/// Gets or sets a value indicating whether the parameter is input-only, output-only, bidirectional, 
		/// or a stored procedure return value parameter.
		/// </summary>
		public ParameterDirection Direction
		{
			get {return this.direction;}
			set {this.direction = value;}
		}

		/// <summary>
		/// Gets or sets a value indicating whether the parameter accepts null values.
		/// </summary>
		public bool IsNullable
		{
			get {return this.isNullable;}
			set {this.isNullable = value;}
		}

		/// <summary>
		/// Gets or sets the number of decimal places to which <see cref="Value"/> is resolved.
		/// </summary>
		public byte Precision
		{
			get {return this.precision;}
			set {this.precision = value;}
		}

		/// <summary>
		/// Gets or sets the number of decimal places to which <see cref="Value"/> is resolved.
		/// </summary>
		public byte Scale
		{
			get {return this.scale;}
			set {this.scale = value;}
		}	

		/// <summary>
		/// Gets or sets the name of the source column that is mapped to the DataSet and used for loading or returning the Value.
		/// </summary>
		public string SourceColumn
		{
			get {return this.sourceColumn;}
			set {this.sourceColumn = value;}
		}
	
		/// <summary>
		/// Gets or sets the <see cref="DataRowVersion"/> to use when loading <see cref="Value"/>.
		/// </summary>
		public DataRowVersion SourceVersion
		{
			get {return this.sourceVersion;}
			set {this.sourceVersion = value;}
		}

		/// <summary>
		/// Gets or sets the value of the parameter.
		/// </summary>
		public object Value
		{
			get {return this.value;}
			set {this.value = value;}
		}

        /// <summary>
        /// Gets or sets how argument is handled and passed to stored procedures.
        /// </summary>
        /// <value>One of values from <see cref="SpArgumentPassMode"/> enumeration. Default is <see cref="SpArgumentPassMode.ArgumentDbType"/>.</value>
        public SpArgumentPassMode PassMode
        {
            get { return this.passMode; }
            set { this.passMode = value; }
        }

		#endregion

		#region Rendering.

		/// <summary>
		/// Gets the rendered parameter name which depends on target DBMS.
		/// Null if the parameter hasn't been rendered yet.
		/// </summary>
		public string RenderedName
		{
			get {return this.renderedName;}
		}

		/// <summary>
		/// Renders the parameter. RenderedName property is set after this method is executed.
		/// </summary>
		/// <param name="dbms">Target DBMS.</param>
		/// <param name="output">StringBuilder to which DBMS dependant parameter name is appended (eg: '@Id' for SQL Server).</param>
		/// <param name="parameters">SQL parameter collection to which this parameter will be added.
		/// Another unique name is automatically given to the parameter if another parameter with the
		/// same name is already in the collection.</param>
		public void Render(DbmsType dbms, StringBuilder output, DbParameterCollection parameters)
		{
			// Each parameter in the collection must have a unique name which is ensured by appending
			// the parameter index to the end of the name. This yields better performance, O(n) complexity,
			// than scanning the collection if a parameter with the same name already exists, O(n2).
			int newParIdx = parameters.Count;

            IParameterRenderer renderer = DbmsComponentFactory.GetComponent<IParameterRenderer>(dbms);
            this.uniqueName = renderer.GetUniqueName(this.originalName, newParIdx);
            this.renderedName = renderer.RenderParameterName(this);			

			parameters.Add(this);
			output.Append(this.renderedName);
		}

		/// <summary>
		/// Renders the parameter name which depends on target DBMS.
		/// </summary>
		/// <param name="dbms">Target DBMS.</param>
		/// <returns>Rendered parameter name.</returns>
		public string RenderParameterName(DbmsType dbms)
		{
            IParameterRenderer renderer = DbmsComponentFactory.GetComponent<IParameterRenderer>(dbms);
			return renderer.RenderParameterName(this);
		}

		#endregion
	}	
}
